This notebook summarizes the findings from assigning cell type labels to all ScPCA samples using SCimilarity.

Running this notebook requires the results from the cell-type-consensus and cell-type-scimilarity module in OpenScPCA-nf to be saved to OpenScPCA-analysis/data/current/results.

suppressPackageStartupMessages({
  # load required packages
  library(ggplot2)
})

# Set default ggplot theme
theme_set(
  theme_classic()
)

# Define color ramp for shared use in the heatmaps
heatmap_col_fun <- circlize::colorRamp2(c(0, 1), colors = c("white", "darkslateblue"))
# Set heatmap padding option
ComplexHeatmap::ht_opt(TITLE_PADDING = grid::unit(0.6, "in"))

# settings
options(
  dplyr.summarise.inform = FALSE, 
  readr.show_col_types = FALSE
)

Functions

# function to read in both scimilarity and consensus results and combine into a single data frame 
prep_data <- function(library_id, scimilarity_file, consensus_file){
  
  scimilarity_df <- readr::read_tsv(scimilarity_file)
  consensus_df <- readr::read_tsv(consensus_file)
  
  annotation_df <- consensus_df |> 
    dplyr::left_join(scimilarity_df, by = c("barcodes" = "barcode")) |> 
    dplyr::mutate(
      cell_id = glue::glue("{library_id}-{barcodes}"), 
      total_cells_per_library = dplyr::n()
    )
  
  return(annotation_df)
  
}
# Copied from scpca-nf template report 
#' Format DT datatables
#'
#' @param df Data frame to format
#'
#' @return DT datatable
format_datatable <- function(df, caption) {
  df |>
    DT::datatable(
      rownames = FALSE,
      extensions = "Scroller",
      caption = caption,
      options = list(
        scroller = TRUE,
        deferRender = TRUE,
        scrollX = TRUE,
        scrollY = 400,
        scrollCollapse = TRUE,
        language = list(search = "Filter:"),
        columnDefs = list(list(
          # render missing values as "NA" rather than blanks in the table
          targets = "_all",
          render = DT::JS(
            "function(data, type, row, meta) {",
            "return data === null ? 'NA' : data;",
            "}"
          )
        ))
      )
    )
}

Data setup

# The base path for the OpenScPCA repository, found by its (hidden) .git directory
repository_base <- rprojroot::find_root(rprojroot::is_git_root)
# results directory with cell-type-consensus 
results_dir <- file.path(repository_base, "data", "current", "results")
consensus_results_dir <- file.path(results_dir, "cell-type-consensus")
scimilarity_results_dir <- file.path(results_dir, "cell-type-scimilarity")

# diagnoses table used for labeling plots from cell-type-consensus module
diagnoses_file <- file.path(repository_base, "analyses", "cell-type-consensus", "sample-info", "project-diagnoses.tsv")
# use the jaccard functions from cell-type-neuroblastoma-04 module
jaccard_functions <- file.path(repository_base, "analyses", "cell-type-neuroblastoma-04", "scripts", "utils", "jaccard-utils.R")
source(jaccard_functions)
# list all results files 
consensus_results_files <- list.files(consensus_results_dir, pattern = "_processed_consensus-cell-types\\.tsv.\\gz$", recursive = TRUE, full.names = TRUE)
consensus_files_df <- data.frame(
  library_id = stringr::word(basename(consensus_results_files), 1, sep = "_"),
  consensus_file = consensus_results_files
)

scimilarity_results_files <- list.files(scimilarity_results_dir, pattern = "_processed_scimilarity-celltype-assignments\\.tsv.\\gz$", recursive = TRUE, full.names = TRUE)
scimilarity_files_df <- data.frame(
  library_id = stringr::word(basename(scimilarity_results_files), 1, sep = "_"),
  scimilarity_file = scimilarity_results_files
)

all_files_df <- scimilarity_files_df |> 
  dplyr::left_join(consensus_files_df, by = "library_id")


# define cell line projects to remove
cell_line_projects <- c("SCPCP000020", "SCPCP000024")
# check that everything has a matching consensus file 
# we joined the consensus into the scimilarity so only libraries with scimilarity are included 
if(sum(is.na(all_files_df$consensus_file)) > 0){
  stop("Missing matching consensus cell type file for all libraries")
}
# read in diagnoses
diagnoses_df <- readr::read_tsv(diagnoses_file)

# read in all results as a single dataframe
results_df <- all_files_df |> 
  purrr::pmap(prep_data) |> 
  dplyr::bind_rows() |> 
  # remove cell line projects
  dplyr::filter(!project_id %in% cell_line_projects) |> 
  # add in diagnoses 
  dplyr::left_join(diagnoses_df, by = "project_id") |> 
  dplyr::mutate(
    # create a label for plotting
    project_label = glue::glue("{project_id}:{diagnosis}")
  )

SCimilarity results

Below we will summarize the results from SCimilarity on all ScPCA projects.

Table of all cell types in ScPCA

First, we just look at the cell types identified and see if any of the cells are not assigned.

sum(is.na(results_df$scimilarity_celltype_annotation))
[1] 0

It looks like every cell gets a cell type, so that’s something to keep in mind when looking at these results.

Let’s see what cell types are presented in our data?

results_df |> 
  dplyr::ungroup() |> 
  dplyr::count(scimilarity_celltype_annotation, name = "total_cells") |> 
  dplyr::arrange(desc(total_cells)) |> 
      format_datatable(caption = "All samples")

So it looks like we have a lot of “native cell” labels which really just means the cell is a cell and didn’t align with anything else in the reference. This label is probably similar to our “other” label in CellAssign.

Also, most of our cells are progenitor cells or hematopoietic stem cells, which either means a lot of the cells are tumor cells and show stem and progenitor like properties or these are from the blood tumors which make up a large part of our atlas. Either way, we have cell types!

Table of cell types per project

This section shows a table of all cell types identified in each project. Each project will have its own table.

# get all the unique labels
project_labels <- unique(results_df$project_label)

tables <- project_labels |> 
  purrr::map(\(label) {
    
    results_df |> 
      dplyr::filter(project_label == label) |> 
      dplyr::group_by(scimilarity_celltype_annotation) |> 
      dplyr::summarise(
        total_cells = dplyr::n()
      ) |> 
      dplyr::arrange(desc(total_cells)) |> 
      format_datatable(caption = label)
    
  })

htmltools::tagList(tables)

Comparison to existing consensus cell types

In this section, we compare the top 15 SCimilarity cell type annotations to the consensus cell types determined using only CellAssign and SingleR annotations for all libraries in a single project. The rows correspond to consensus cell types and the columns correspond to SCimilarity annotations. All other cell types not in the top 10 represented cell types in a project are grouped into the “All remaining cell types” category.

results_df <- results_df |> 
    dplyr::group_by(project_id) |> 
    dplyr::mutate(
      # get most frequently observed cell types across libraries in that project 
      scimilarity_celltype_lumped = forcats::fct_lump_n(scimilarity_celltype_annotation, 15, other_level = "All remaining cell types", ties.method = "first") |> 
        # sort by frequency 
        forcats::fct_infreq() |> 
        # make sure all remaining and unknown are last, use this to assign colors in specific order
        forcats::fct_relevel("All remaining cell types", after = Inf) |> 
        as.character()
    )
project_labels |> 
  purrr::walk(\(label){
    
    results_df |> 
      dplyr::filter(project_label == label) |> 
      make_jaccard_heatmap(
        annotation_col1 = "scimilarity_celltype_lumped",
        annotation_col2 = "consensus_annotation",
        label1 = glue::glue("{label} \nSCimilarity"),
        label2 = "Consensus"
      )
    
  })

As expected there is some direct agreement or places where more broad consensus cell types are split up across more granular cell types in SCimilarity. A few notable examples:

  • SCPCP000001: Cells labeled as macrophages in consensus are split between glial and microglial cells in SCimilarity. Natural killer cells match up between consensus and SCimilarity.
  • SCPCP000006: Endothelial cells match up between consensus and SCimilarity. The unknown cells in consensus correspond to mesenchymal stem cells and a variety of kidney cells.
  • SCPCP000007: Mature T cell in consensus is split between CD4 and CD8 T cells in SCimilarity.

We also see places where there is not agreement, which is to be expected.

Conclusions

Of the 203 possible cell types in SCimilarity, 191 of them are represented in ScPCA samples. There are quite a few scenarios where consensus cell types “agree” with SCimilarity. It will be informative to look at the new consensus cell types and how they change with the addition of the SCimilarity annotations, but in general the addition of SCimilarity seems quite useful!

Session info

# record the versions of the packages used in this analysis and other environment information
sessionInfo()
R version 4.4.2 (2024-10-31)
Platform: aarch64-apple-darwin20
Running under: macOS Sequoia 15.6.1

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/Chicago
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
[1] ggplot2_3.5.2

loaded via a namespace (and not attached):
 [1] tidyr_1.3.1           sass_0.4.10           generics_0.1.4        renv_1.1.4            shape_1.4.6.1         stringi_1.8.7         hms_1.1.3             digest_0.6.37         magrittr_2.0.3        evaluate_1.0.5       
[11] grid_4.4.2            RColorBrewer_1.1-3    iterators_1.0.14      circlize_0.4.16       fastmap_1.2.0         rprojroot_2.1.0       foreach_1.5.2         doParallel_1.0.17     jsonlite_2.0.0        GlobalOptions_0.1.2  
[21] BiocManager_1.30.26   ComplexHeatmap_2.20.0 purrr_1.1.0           crosstalk_1.2.2       scales_1.4.0          codetools_0.2-20      jquerylib_0.1.4       cli_3.6.5             rlang_1.1.6           crayon_1.5.3         
[31] bit64_4.6.0-1         withr_3.0.2           cachem_1.1.0          yaml_2.3.10           tools_4.4.2           parallel_4.4.2        tzdb_0.5.0            dplyr_1.1.4           colorspace_2.1-1      DT_0.34.0            
[41] forcats_1.0.0         GetoptLong_1.0.5      BiocGenerics_0.50.0   vctrs_0.6.5           R6_2.6.1              png_0.1-8             matrixStats_1.5.0     stats4_4.4.2          lifecycle_1.0.4       stringr_1.5.1        
[51] htmlwidgets_1.6.4     bit_4.6.0             S4Vectors_0.42.1      IRanges_2.38.1        vroom_1.6.5           clue_0.3-66           cluster_2.1.8.1       pkgconfig_2.0.3       pillar_1.11.0         bslib_0.9.0          
[61] gtable_0.3.6          glue_1.8.0            xfun_0.53             tibble_3.3.0          tidyselect_1.2.1      knitr_1.50            farver_2.1.2          rjson_0.2.23          htmltools_0.5.8.1     rmarkdown_2.29       
[71] readr_2.1.5           compiler_4.4.2       
LS0tCnRpdGxlOiAiRXhwbG9yZSBTQ2ltaWxhcml0eSBjZWxsIHR5cGUgYW5ub3RhdGlvbnMiCmF1dGhvcjogQWxseSBIYXdraW5zCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoKVGhpcyBub3RlYm9vayBzdW1tYXJpemVzIHRoZSBmaW5kaW5ncyBmcm9tIGFzc2lnbmluZyBjZWxsIHR5cGUgbGFiZWxzIHRvIGFsbCBTY1BDQSBzYW1wbGVzIHVzaW5nIGBTQ2ltaWxhcml0eWAuIAoKUnVubmluZyB0aGlzIG5vdGVib29rIHJlcXVpcmVzIHRoZSByZXN1bHRzIGZyb20gdGhlIGBjZWxsLXR5cGUtY29uc2Vuc3VzYCBhbmQgYGNlbGwtdHlwZS1zY2ltaWxhcml0eWAgbW9kdWxlIGluIGBPcGVuU2NQQ0EtbmZgIHRvIGJlIHNhdmVkIHRvIGBPcGVuU2NQQ0EtYW5hbHlzaXMvZGF0YS9jdXJyZW50L3Jlc3VsdHNgLiAKCmBgYHtyIHBhY2thZ2VzfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogICMgbG9hZCByZXF1aXJlZCBwYWNrYWdlcwogIGxpYnJhcnkoZ2dwbG90MikKfSkKCiMgU2V0IGRlZmF1bHQgZ2dwbG90IHRoZW1lCnRoZW1lX3NldCgKICB0aGVtZV9jbGFzc2ljKCkKKQoKIyBEZWZpbmUgY29sb3IgcmFtcCBmb3Igc2hhcmVkIHVzZSBpbiB0aGUgaGVhdG1hcHMKaGVhdG1hcF9jb2xfZnVuIDwtIGNpcmNsaXplOjpjb2xvclJhbXAyKGMoMCwgMSksIGNvbG9ycyA9IGMoIndoaXRlIiwgImRhcmtzbGF0ZWJsdWUiKSkKIyBTZXQgaGVhdG1hcCBwYWRkaW5nIG9wdGlvbgpDb21wbGV4SGVhdG1hcDo6aHRfb3B0KFRJVExFX1BBRERJTkcgPSBncmlkOjp1bml0KDAuNiwgImluIikpCgojIHNldHRpbmdzCm9wdGlvbnMoCiAgZHBseXIuc3VtbWFyaXNlLmluZm9ybSA9IEZBTFNFLCAKICByZWFkci5zaG93X2NvbF90eXBlcyA9IEZBTFNFCikKCgpgYGAKCiMjIEZ1bmN0aW9ucwoKCmBgYHtyfQojIGZ1bmN0aW9uIHRvIHJlYWQgaW4gYm90aCBzY2ltaWxhcml0eSBhbmQgY29uc2Vuc3VzIHJlc3VsdHMgYW5kIGNvbWJpbmUgaW50byBhIHNpbmdsZSBkYXRhIGZyYW1lIApwcmVwX2RhdGEgPC0gZnVuY3Rpb24obGlicmFyeV9pZCwgc2NpbWlsYXJpdHlfZmlsZSwgY29uc2Vuc3VzX2ZpbGUpewogIAogIHNjaW1pbGFyaXR5X2RmIDwtIHJlYWRyOjpyZWFkX3RzdihzY2ltaWxhcml0eV9maWxlKQogIGNvbnNlbnN1c19kZiA8LSByZWFkcjo6cmVhZF90c3YoY29uc2Vuc3VzX2ZpbGUpCiAgCiAgYW5ub3RhdGlvbl9kZiA8LSBjb25zZW5zdXNfZGYgfD4gCiAgICBkcGx5cjo6bGVmdF9qb2luKHNjaW1pbGFyaXR5X2RmLCBieSA9IGMoImJhcmNvZGVzIiA9ICJiYXJjb2RlIikpIHw+IAogICAgZHBseXI6Om11dGF0ZSgKICAgICAgY2VsbF9pZCA9IGdsdWU6OmdsdWUoIntsaWJyYXJ5X2lkfS17YmFyY29kZXN9IiksIAogICAgICB0b3RhbF9jZWxsc19wZXJfbGlicmFyeSA9IGRwbHlyOjpuKCkKICAgICkKICAKICByZXR1cm4oYW5ub3RhdGlvbl9kZikKICAKfQpgYGAKCmBgYHtyfQojIENvcGllZCBmcm9tIHNjcGNhLW5mIHRlbXBsYXRlIHJlcG9ydCAKIycgRm9ybWF0IERUIGRhdGF0YWJsZXMKIycKIycgQHBhcmFtIGRmIERhdGEgZnJhbWUgdG8gZm9ybWF0CiMnCiMnIEByZXR1cm4gRFQgZGF0YXRhYmxlCmZvcm1hdF9kYXRhdGFibGUgPC0gZnVuY3Rpb24oZGYsIGNhcHRpb24pIHsKICBkZiB8PgogICAgRFQ6OmRhdGF0YWJsZSgKICAgICAgcm93bmFtZXMgPSBGQUxTRSwKICAgICAgZXh0ZW5zaW9ucyA9ICJTY3JvbGxlciIsCiAgICAgIGNhcHRpb24gPSBjYXB0aW9uLAogICAgICBvcHRpb25zID0gbGlzdCgKICAgICAgICBzY3JvbGxlciA9IFRSVUUsCiAgICAgICAgZGVmZXJSZW5kZXIgPSBUUlVFLAogICAgICAgIHNjcm9sbFggPSBUUlVFLAogICAgICAgIHNjcm9sbFkgPSA0MDAsCiAgICAgICAgc2Nyb2xsQ29sbGFwc2UgPSBUUlVFLAogICAgICAgIGxhbmd1YWdlID0gbGlzdChzZWFyY2ggPSAiRmlsdGVyOiIpLAogICAgICAgIGNvbHVtbkRlZnMgPSBsaXN0KGxpc3QoCiAgICAgICAgICAjIHJlbmRlciBtaXNzaW5nIHZhbHVlcyBhcyAiTkEiIHJhdGhlciB0aGFuIGJsYW5rcyBpbiB0aGUgdGFibGUKICAgICAgICAgIHRhcmdldHMgPSAiX2FsbCIsCiAgICAgICAgICByZW5kZXIgPSBEVDo6SlMoCiAgICAgICAgICAgICJmdW5jdGlvbihkYXRhLCB0eXBlLCByb3csIG1ldGEpIHsiLAogICAgICAgICAgICAicmV0dXJuIGRhdGEgPT09IG51bGwgPyAnTkEnIDogZGF0YTsiLAogICAgICAgICAgICAifSIKICAgICAgICAgICkKICAgICAgICApKQogICAgICApCiAgICApCn0KYGBgCgoKCiMjIERhdGEgc2V0dXAKCgpgYGB7ciBiYXNlIHBhdGhzfQojIFRoZSBiYXNlIHBhdGggZm9yIHRoZSBPcGVuU2NQQ0EgcmVwb3NpdG9yeSwgZm91bmQgYnkgaXRzIChoaWRkZW4pIC5naXQgZGlyZWN0b3J5CnJlcG9zaXRvcnlfYmFzZSA8LSBycHJvanJvb3Q6OmZpbmRfcm9vdChycHJvanJvb3Q6OmlzX2dpdF9yb290KQojIHJlc3VsdHMgZGlyZWN0b3J5IHdpdGggY2VsbC10eXBlLWNvbnNlbnN1cyAKcmVzdWx0c19kaXIgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImRhdGEiLCAiY3VycmVudCIsICJyZXN1bHRzIikKY29uc2Vuc3VzX3Jlc3VsdHNfZGlyIDwtIGZpbGUucGF0aChyZXN1bHRzX2RpciwgImNlbGwtdHlwZS1jb25zZW5zdXMiKQpzY2ltaWxhcml0eV9yZXN1bHRzX2RpciA8LSBmaWxlLnBhdGgocmVzdWx0c19kaXIsICJjZWxsLXR5cGUtc2NpbWlsYXJpdHkiKQoKIyBkaWFnbm9zZXMgdGFibGUgdXNlZCBmb3IgbGFiZWxpbmcgcGxvdHMgZnJvbSBjZWxsLXR5cGUtY29uc2Vuc3VzIG1vZHVsZQpkaWFnbm9zZXNfZmlsZSA8LSBmaWxlLnBhdGgocmVwb3NpdG9yeV9iYXNlLCAiYW5hbHlzZXMiLCAiY2VsbC10eXBlLWNvbnNlbnN1cyIsICJzYW1wbGUtaW5mbyIsICJwcm9qZWN0LWRpYWdub3Nlcy50c3YiKQpgYGAKCmBgYHtyfQojIHVzZSB0aGUgamFjY2FyZCBmdW5jdGlvbnMgZnJvbSBjZWxsLXR5cGUtbmV1cm9ibGFzdG9tYS0wNCBtb2R1bGUKamFjY2FyZF9mdW5jdGlvbnMgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImFuYWx5c2VzIiwgImNlbGwtdHlwZS1uZXVyb2JsYXN0b21hLTA0IiwgInNjcmlwdHMiLCAidXRpbHMiLCAiamFjY2FyZC11dGlscy5SIikKc291cmNlKGphY2NhcmRfZnVuY3Rpb25zKQpgYGAKCgpgYGB7cn0KIyBsaXN0IGFsbCByZXN1bHRzIGZpbGVzIApjb25zZW5zdXNfcmVzdWx0c19maWxlcyA8LSBsaXN0LmZpbGVzKGNvbnNlbnN1c19yZXN1bHRzX2RpciwgcGF0dGVybiA9ICJfcHJvY2Vzc2VkX2NvbnNlbnN1cy1jZWxsLXR5cGVzXFwudHN2LlxcZ3okIiwgcmVjdXJzaXZlID0gVFJVRSwgZnVsbC5uYW1lcyA9IFRSVUUpCmNvbnNlbnN1c19maWxlc19kZiA8LSBkYXRhLmZyYW1lKAogIGxpYnJhcnlfaWQgPSBzdHJpbmdyOjp3b3JkKGJhc2VuYW1lKGNvbnNlbnN1c19yZXN1bHRzX2ZpbGVzKSwgMSwgc2VwID0gIl8iKSwKICBjb25zZW5zdXNfZmlsZSA9IGNvbnNlbnN1c19yZXN1bHRzX2ZpbGVzCikKCnNjaW1pbGFyaXR5X3Jlc3VsdHNfZmlsZXMgPC0gbGlzdC5maWxlcyhzY2ltaWxhcml0eV9yZXN1bHRzX2RpciwgcGF0dGVybiA9ICJfcHJvY2Vzc2VkX3NjaW1pbGFyaXR5LWNlbGx0eXBlLWFzc2lnbm1lbnRzXFwudHN2LlxcZ3okIiwgcmVjdXJzaXZlID0gVFJVRSwgZnVsbC5uYW1lcyA9IFRSVUUpCnNjaW1pbGFyaXR5X2ZpbGVzX2RmIDwtIGRhdGEuZnJhbWUoCiAgbGlicmFyeV9pZCA9IHN0cmluZ3I6OndvcmQoYmFzZW5hbWUoc2NpbWlsYXJpdHlfcmVzdWx0c19maWxlcyksIDEsIHNlcCA9ICJfIiksCiAgc2NpbWlsYXJpdHlfZmlsZSA9IHNjaW1pbGFyaXR5X3Jlc3VsdHNfZmlsZXMKKQoKYWxsX2ZpbGVzX2RmIDwtIHNjaW1pbGFyaXR5X2ZpbGVzX2RmIHw+IAogIGRwbHlyOjpsZWZ0X2pvaW4oY29uc2Vuc3VzX2ZpbGVzX2RmLCBieSA9ICJsaWJyYXJ5X2lkIikKCgojIGRlZmluZSBjZWxsIGxpbmUgcHJvamVjdHMgdG8gcmVtb3ZlCmNlbGxfbGluZV9wcm9qZWN0cyA8LSBjKCJTQ1BDUDAwMDAyMCIsICJTQ1BDUDAwMDAyNCIpCmBgYAoKYGBge3J9CiMgY2hlY2sgdGhhdCBldmVyeXRoaW5nIGhhcyBhIG1hdGNoaW5nIGNvbnNlbnN1cyBmaWxlIAojIHdlIGpvaW5lZCB0aGUgY29uc2Vuc3VzIGludG8gdGhlIHNjaW1pbGFyaXR5IHNvIG9ubHkgbGlicmFyaWVzIHdpdGggc2NpbWlsYXJpdHkgYXJlIGluY2x1ZGVkIAppZihzdW0oaXMubmEoYWxsX2ZpbGVzX2RmJGNvbnNlbnN1c19maWxlKSkgPiAwKXsKICBzdG9wKCJNaXNzaW5nIG1hdGNoaW5nIGNvbnNlbnN1cyBjZWxsIHR5cGUgZmlsZSBmb3IgYWxsIGxpYnJhcmllcyIpCn0KYGBgCgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgcmVhZCBpbiBkaWFnbm9zZXMKZGlhZ25vc2VzX2RmIDwtIHJlYWRyOjpyZWFkX3RzdihkaWFnbm9zZXNfZmlsZSkKCiMgcmVhZCBpbiBhbGwgcmVzdWx0cyBhcyBhIHNpbmdsZSBkYXRhZnJhbWUKcmVzdWx0c19kZiA8LSBhbGxfZmlsZXNfZGYgfD4gCiAgcHVycnI6OnBtYXAocHJlcF9kYXRhKSB8PiAKICBkcGx5cjo6YmluZF9yb3dzKCkgfD4gCiAgIyByZW1vdmUgY2VsbCBsaW5lIHByb2plY3RzCiAgZHBseXI6OmZpbHRlcighcHJvamVjdF9pZCAlaW4lIGNlbGxfbGluZV9wcm9qZWN0cykgfD4gCiAgIyBhZGQgaW4gZGlhZ25vc2VzIAogIGRwbHlyOjpsZWZ0X2pvaW4oZGlhZ25vc2VzX2RmLCBieSA9ICJwcm9qZWN0X2lkIikgfD4gCiAgZHBseXI6Om11dGF0ZSgKICAgICMgY3JlYXRlIGEgbGFiZWwgZm9yIHBsb3R0aW5nCiAgICBwcm9qZWN0X2xhYmVsID0gZ2x1ZTo6Z2x1ZSgie3Byb2plY3RfaWR9OntkaWFnbm9zaXN9IikKICApCmBgYAoKIyMgYFNDaW1pbGFyaXR5YCByZXN1bHRzIAoKQmVsb3cgd2Ugd2lsbCBzdW1tYXJpemUgdGhlIHJlc3VsdHMgZnJvbSBgU0NpbWlsYXJpdHlgIG9uIGFsbCBTY1BDQSBwcm9qZWN0cy4gCgojIyMgVGFibGUgb2YgYWxsIGNlbGwgdHlwZXMgaW4gU2NQQ0EgCgpGaXJzdCwgd2UganVzdCBsb29rIGF0IHRoZSBjZWxsIHR5cGVzIGlkZW50aWZpZWQgYW5kIHNlZSBpZiBhbnkgb2YgdGhlIGNlbGxzIGFyZSBub3QgYXNzaWduZWQuIAoKYGBge3J9CnN1bShpcy5uYShyZXN1bHRzX2RmJHNjaW1pbGFyaXR5X2NlbGx0eXBlX2Fubm90YXRpb24pKQpgYGAKCkl0IGxvb2tzIGxpa2UgZXZlcnkgY2VsbCBnZXRzIGEgY2VsbCB0eXBlLCBzbyB0aGF0J3Mgc29tZXRoaW5nIHRvIGtlZXAgaW4gbWluZCB3aGVuIGxvb2tpbmcgYXQgdGhlc2UgcmVzdWx0cy4gCgpMZXQncyBzZWUgd2hhdCBjZWxsIHR5cGVzIGFyZSBwcmVzZW50ZWQgaW4gb3VyIGRhdGE/IAoKYGBge3J9CnJlc3VsdHNfZGYgfD4gCiAgZHBseXI6OnVuZ3JvdXAoKSB8PiAKICBkcGx5cjo6Y291bnQoc2NpbWlsYXJpdHlfY2VsbHR5cGVfYW5ub3RhdGlvbiwgbmFtZSA9ICJ0b3RhbF9jZWxscyIpIHw+IAogIGRwbHlyOjphcnJhbmdlKGRlc2ModG90YWxfY2VsbHMpKSB8PiAKICAgICAgZm9ybWF0X2RhdGF0YWJsZShjYXB0aW9uID0gIkFsbCBzYW1wbGVzIikKYGBgCgpTbyBpdCBsb29rcyBsaWtlIHdlIGhhdmUgYSBsb3Qgb2YgIm5hdGl2ZSBjZWxsIiBsYWJlbHMgd2hpY2ggcmVhbGx5IGp1c3QgbWVhbnMgdGhlIGNlbGwgaXMgYSBjZWxsIGFuZCBkaWRuJ3QgYWxpZ24gd2l0aCBhbnl0aGluZyBlbHNlIGluIHRoZSByZWZlcmVuY2UuIApUaGlzIGxhYmVsIGlzIHByb2JhYmx5IHNpbWlsYXIgdG8gb3VyICJvdGhlciIgbGFiZWwgaW4gYENlbGxBc3NpZ25gLiAKCkFsc28sIG1vc3Qgb2Ygb3VyIGNlbGxzIGFyZSBwcm9nZW5pdG9yIGNlbGxzIG9yIGhlbWF0b3BvaWV0aWMgc3RlbSBjZWxscywgd2hpY2ggZWl0aGVyIG1lYW5zIGEgbG90IG9mIHRoZSBjZWxscyBhcmUgdHVtb3IgY2VsbHMgYW5kIHNob3cgc3RlbSBhbmQgcHJvZ2VuaXRvciBsaWtlIHByb3BlcnRpZXMgb3IgdGhlc2UgYXJlIGZyb20gdGhlIGJsb29kIHR1bW9ycyB3aGljaCBtYWtlIHVwIGEgbGFyZ2UgcGFydCBvZiBvdXIgYXRsYXMuCkVpdGhlciB3YXksIHdlIGhhdmUgY2VsbCB0eXBlcyEgCgojIyMgVGFibGUgb2YgY2VsbCB0eXBlcyBwZXIgcHJvamVjdCAKClRoaXMgc2VjdGlvbiBzaG93cyBhIHRhYmxlIG9mIGFsbCBjZWxsIHR5cGVzIGlkZW50aWZpZWQgaW4gZWFjaCBwcm9qZWN0LiAKRWFjaCBwcm9qZWN0IHdpbGwgaGF2ZSBpdHMgb3duIHRhYmxlLiAKCmBgYHtyfQojIGdldCBhbGwgdGhlIHVuaXF1ZSBsYWJlbHMKcHJvamVjdF9sYWJlbHMgPC0gdW5pcXVlKHJlc3VsdHNfZGYkcHJvamVjdF9sYWJlbCkKCnRhYmxlcyA8LSBwcm9qZWN0X2xhYmVscyB8PiAKICBwdXJycjo6bWFwKFwobGFiZWwpIHsKICAgIAogICAgcmVzdWx0c19kZiB8PiAKICAgICAgZHBseXI6OmZpbHRlcihwcm9qZWN0X2xhYmVsID09IGxhYmVsKSB8PiAKICAgICAgZHBseXI6Omdyb3VwX2J5KHNjaW1pbGFyaXR5X2NlbGx0eXBlX2Fubm90YXRpb24pIHw+IAogICAgICBkcGx5cjo6c3VtbWFyaXNlKAogICAgICAgIHRvdGFsX2NlbGxzID0gZHBseXI6Om4oKQogICAgICApIHw+IAogICAgICBkcGx5cjo6YXJyYW5nZShkZXNjKHRvdGFsX2NlbGxzKSkgfD4gCiAgICAgIGZvcm1hdF9kYXRhdGFibGUoY2FwdGlvbiA9IGxhYmVsKQogICAgCiAgfSkKCmh0bWx0b29sczo6dGFnTGlzdCh0YWJsZXMpCmBgYAoKIyMjIENvbXBhcmlzb24gdG8gZXhpc3RpbmcgY29uc2Vuc3VzIGNlbGwgdHlwZXMgCgpJbiB0aGlzIHNlY3Rpb24sIHdlIGNvbXBhcmUgdGhlIHRvcCAxNSBgU0NpbWlsYXJpdHlgIGNlbGwgdHlwZSBhbm5vdGF0aW9ucyB0byB0aGUgY29uc2Vuc3VzIGNlbGwgdHlwZXMgZGV0ZXJtaW5lZCB1c2luZyBvbmx5IGBDZWxsQXNzaWduYCBhbmQgYFNpbmdsZVJgIGFubm90YXRpb25zIGZvciBhbGwgbGlicmFyaWVzIGluIGEgc2luZ2xlIHByb2plY3QuIApUaGUgcm93cyBjb3JyZXNwb25kIHRvIGNvbnNlbnN1cyBjZWxsIHR5cGVzIGFuZCB0aGUgY29sdW1ucyBjb3JyZXNwb25kIHRvIGBTQ2ltaWxhcml0eWAgYW5ub3RhdGlvbnMuIApBbGwgb3RoZXIgY2VsbCB0eXBlcyBub3QgaW4gdGhlIHRvcCAxMCByZXByZXNlbnRlZCBjZWxsIHR5cGVzIGluIGEgcHJvamVjdCBhcmUgZ3JvdXBlZCBpbnRvIHRoZSAiQWxsIHJlbWFpbmluZyBjZWxsIHR5cGVzIiBjYXRlZ29yeS4gCgpgYGB7cn0KcmVzdWx0c19kZiA8LSByZXN1bHRzX2RmIHw+IAogICAgZHBseXI6Omdyb3VwX2J5KHByb2plY3RfaWQpIHw+IAogICAgZHBseXI6Om11dGF0ZSgKICAgICAgIyBnZXQgbW9zdCBmcmVxdWVudGx5IG9ic2VydmVkIGNlbGwgdHlwZXMgYWNyb3NzIGxpYnJhcmllcyBpbiB0aGF0IHByb2plY3QgCiAgICAgIHNjaW1pbGFyaXR5X2NlbGx0eXBlX2x1bXBlZCA9IGZvcmNhdHM6OmZjdF9sdW1wX24oc2NpbWlsYXJpdHlfY2VsbHR5cGVfYW5ub3RhdGlvbiwgMTUsIG90aGVyX2xldmVsID0gIkFsbCByZW1haW5pbmcgY2VsbCB0eXBlcyIsIHRpZXMubWV0aG9kID0gImZpcnN0IikgfD4gCiAgICAgICAgIyBzb3J0IGJ5IGZyZXF1ZW5jeSAKICAgICAgICBmb3JjYXRzOjpmY3RfaW5mcmVxKCkgfD4gCiAgICAgICAgIyBtYWtlIHN1cmUgYWxsIHJlbWFpbmluZyBhbmQgdW5rbm93biBhcmUgbGFzdCwgdXNlIHRoaXMgdG8gYXNzaWduIGNvbG9ycyBpbiBzcGVjaWZpYyBvcmRlcgogICAgICAgIGZvcmNhdHM6OmZjdF9yZWxldmVsKCJBbGwgcmVtYWluaW5nIGNlbGwgdHlwZXMiLCBhZnRlciA9IEluZikgfD4gCiAgICAgICAgYXMuY2hhcmFjdGVyKCkKICAgICkKYGBgCgoKYGBge3IsIGZpZy5oZWlnaHQgPSAxMCwgZmlnLndpZHRoID0gMTB9CnByb2plY3RfbGFiZWxzIHw+IAogIHB1cnJyOjp3YWxrKFwobGFiZWwpewogICAgCiAgICByZXN1bHRzX2RmIHw+IAogICAgICBkcGx5cjo6ZmlsdGVyKHByb2plY3RfbGFiZWwgPT0gbGFiZWwpIHw+IAogICAgICBtYWtlX2phY2NhcmRfaGVhdG1hcCgKICAgICAgICBhbm5vdGF0aW9uX2NvbDEgPSAic2NpbWlsYXJpdHlfY2VsbHR5cGVfbHVtcGVkIiwKICAgICAgICBhbm5vdGF0aW9uX2NvbDIgPSAiY29uc2Vuc3VzX2Fubm90YXRpb24iLAogICAgICAgIGxhYmVsMSA9IGdsdWU6OmdsdWUoIntsYWJlbH0gXG5TQ2ltaWxhcml0eSIpLAogICAgICAgIGxhYmVsMiA9ICJDb25zZW5zdXMiCiAgICAgICkKICAgIAogIH0pCmBgYAoKQXMgZXhwZWN0ZWQgdGhlcmUgaXMgc29tZSBkaXJlY3QgYWdyZWVtZW50IG9yIHBsYWNlcyB3aGVyZSBtb3JlIGJyb2FkIGNvbnNlbnN1cyBjZWxsIHR5cGVzIGFyZSBzcGxpdCB1cCBhY3Jvc3MgbW9yZSBncmFudWxhciBjZWxsIHR5cGVzIGluIGBTQ2ltaWxhcml0eWAuIApBIGZldyBub3RhYmxlIGV4YW1wbGVzOiAKCi0gYFNDUENQMDAwMDAxYDogQ2VsbHMgbGFiZWxlZCBhcyBtYWNyb3BoYWdlcyBpbiBjb25zZW5zdXMgYXJlIHNwbGl0IGJldHdlZW4gZ2xpYWwgYW5kIG1pY3JvZ2xpYWwgY2VsbHMgaW4gYFNDaW1pbGFyaXR5YC4gCk5hdHVyYWwga2lsbGVyIGNlbGxzIG1hdGNoIHVwIGJldHdlZW4gY29uc2Vuc3VzIGFuZCBgU0NpbWlsYXJpdHlgLiAKLSBgU0NQQ1AwMDAwMDZgOiBFbmRvdGhlbGlhbCBjZWxscyBtYXRjaCB1cCBiZXR3ZWVuIGNvbnNlbnN1cyBhbmQgYFNDaW1pbGFyaXR5YC4gClRoZSB1bmtub3duIGNlbGxzIGluIGNvbnNlbnN1cyBjb3JyZXNwb25kIHRvIG1lc2VuY2h5bWFsIHN0ZW0gY2VsbHMgYW5kIGEgdmFyaWV0eSBvZiBraWRuZXkgY2VsbHMuIAotIGBTQ1BDUDAwMDAwN2A6IE1hdHVyZSBUIGNlbGwgaW4gY29uc2Vuc3VzIGlzIHNwbGl0IGJldHdlZW4gQ0Q0IGFuZCBDRDggVCBjZWxscyBpbiBgU0NpbWlsYXJpdHlgLiAKCldlIGFsc28gc2VlIHBsYWNlcyB3aGVyZSB0aGVyZSBpcyBub3QgYWdyZWVtZW50LCB3aGljaCBpcyB0byBiZSBleHBlY3RlZC4KCiMjIENvbmNsdXNpb25zCgpPZiB0aGUgMjAzIHBvc3NpYmxlIGNlbGwgdHlwZXMgaW4gYFNDaW1pbGFyaXR5YCwgMTkxIG9mIHRoZW0gYXJlIHJlcHJlc2VudGVkIGluIFNjUENBIHNhbXBsZXMuIApUaGVyZSBhcmUgcXVpdGUgYSBmZXcgc2NlbmFyaW9zIHdoZXJlIGNvbnNlbnN1cyBjZWxsIHR5cGVzICJhZ3JlZSIgd2l0aCBgU0NpbWlsYXJpdHlgLiAKSXQgd2lsbCBiZSBpbmZvcm1hdGl2ZSB0byBsb29rIGF0IHRoZSBuZXcgY29uc2Vuc3VzIGNlbGwgdHlwZXMgYW5kIGhvdyB0aGV5IGNoYW5nZSB3aXRoIHRoZSBhZGRpdGlvbiBvZiB0aGUgYFNDaW1pbGFyaXR5YCBhbm5vdGF0aW9ucywgYnV0IGluIGdlbmVyYWwgdGhlIGFkZGl0aW9uIG9mIGBTQ2ltaWxhcml0eWAgc2VlbXMgcXVpdGUgdXNlZnVsIQoKIyMgU2Vzc2lvbiBpbmZvIAoKYGBge3Igc2Vzc2lvbiBpbmZvfQojIHJlY29yZCB0aGUgdmVyc2lvbnMgb2YgdGhlIHBhY2thZ2VzIHVzZWQgaW4gdGhpcyBhbmFseXNpcyBhbmQgb3RoZXIgZW52aXJvbm1lbnQgaW5mb3JtYXRpb24Kc2Vzc2lvbkluZm8oKQpgYGAKCgo=